/*
 * Copyright 2025 author or author.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jcwang.commons.tools.util.xss;

import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.springframework.lang.Nullable;

/**
 * SVG XSS 验证器
 *
 * @author 王金城
 **/
public class SvgXssValidator {

    /**
     * 基础SVG白名单
     * 只包含最基本的SVG元素和属性，不包含任何脚本或事件处理相关的内容
     */
    private static final Safelist BASIC_SVG = basicSvg();
    ;
    /**
     * 宽松的SVG白名单
     * 允许更多SVG元素和属性，但仍排除潜在危险的内容
     */
    private static final Safelist RELAXED_SVG = relaxedSvg();
    /**
     * 创建严格的SVG白名单
     * 仅允许最基础且安全的SVG元素和属性
     **/
    private static final Safelist STRICT_SVG = strictSvg();


    /**
     * 验证SVG内容是否符合基本安全标准
     * 使用基础白名单过滤，防止XSS攻击
     *
     * @param svgContent SVG内容字符串，为空则会直接返回 true
     * @return true-表示内容安全且格式正确，false-表示存在安全风险或格式错误
     */
    public static boolean isSvgSafeBasic(@Nullable String svgContent) {
        if (StringUtils.isBlank(svgContent)) {
            return true;
        }
        return Jsoup.isValid(svgContent, BASIC_SVG);
    }


    /**
     * 验证SVG内容是否符合宽松安全标准
     * 允许更多SVG特性，同时保持基本安全防护
     *
     * @param svgContent SVG内容字符串，为空则会直接返回 true
     * @return true-表示内容安全且格式正确，false-表示存在安全风险或格式错误
     */
    public static boolean isSvgSafeRelaxed(@Nullable String svgContent) {
        if (StringUtils.isBlank(svgContent)) {
            return true;
        }
        return Jsoup.isValid(svgContent, RELAXED_SVG);
    }

    /**
     * 验证SVG内容是否符合严格安全标准
     * 仅允许最基本的SVG元素，提供最高级别的安全防护
     *
     * @param svgContent SVG内容字符串，为空则会直接返回 true
     * @return true-表示内容安全且格式正确，false-表示存在安全风险或格式错误
     */
    public static boolean isSvgSafeStrict(@Nullable String svgContent) {
        if (StringUtils.isBlank(svgContent)) {
            return true;
        }
        return Jsoup.isValid(svgContent, STRICT_SVG);
    }


    /**
     * 创建基础SVG白名单
     * 只包含最基本的SVG元素和属性，不包含任何脚本或事件处理相关的内容
     *
     * @return Safelist 基础SVG白名单
     */
    private static Safelist basicSvg() {
        return new Safelist()
                // 基本SVG元素
                .addTags("svg", "g", "defs", "symbol", "use", "image", "switch")
                // 图形元素
                .addTags("path", "rect", "circle", "ellipse", "line", "polyline", "polygon")
                // 文本元素
                .addTags("text", "tspan", "textPath", "tref", "altGlyph", "altGlyphDef", "altGlyphItem", "glyph", "glyphRef")
                // 样式元素
                .addTags("style")
                // 渐变和图案
                .addTags("linearGradient", "radialGradient", "stop", "pattern")
                // 动画元素（不包含事件处理）
                .addTags("animate", "animateColor", "animateMotion", "animateTransform", "set")
                // 蒙版和剪切
                .addTags("mask", "clipPath")
                // 滤镜
                .addTags("filter", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite",
                        "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight",
                        "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur",
                        "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight",
                        "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence")
                // 基本属性
                .addAttributes("svg", "xmlns", "version", "width", "height", "viewBox", "preserveAspectRatio")
                .addAttributes("g", "transform")
                .addAttributes("path", "d", "fill", "stroke", "stroke-width", "fill-rule")
                .addAttributes("rect", "x", "y", "width", "height", "rx", "ry", "fill", "stroke", "stroke-width")
                .addAttributes("circle", "cx", "cy", "r", "fill", "stroke", "stroke-width")
                .addAttributes("ellipse", "cx", "cy", "rx", "ry", "fill", "stroke", "stroke-width")
                .addAttributes("line", "x1", "y1", "x2", "y2", "stroke", "stroke-width")
                .addAttributes("polyline", "points", "fill", "stroke", "stroke-width")
                .addAttributes("polygon", "points", "fill", "stroke", "stroke-width")
                .addAttributes("text", "x", "y", "dx", "dy", "text-anchor", "font-family", "font-size", "fill")
                .addAttributes("tspan", "x", "y", "dx", "dy")
                .addAttributes("image", "x", "y", "width", "height", "href", "xlink:href")
                .addAttributes("use", "x", "y", "width", "height", "href", "xlink:href")
                .addAttributes("linearGradient", "id", "x1", "y1", "x2", "y2", "gradientUnits")
                .addAttributes("radialGradient", "id", "cx", "cy", "r", "fx", "fy", "gradientUnits")
                .addAttributes("stop", "offset", "stop-color", "stop-opacity")
                .addAttributes("animate", "attributeName", "from", "to", "dur", "repeatCount")
                .addAttributes("style", "type")
                // URL协议白名单（仅允许安全的协议）
                .addProtocols("image", "href", "http", "https", "data")
                .addProtocols("image", "xlink:href", "http", "https", "data")
                .addProtocols("use", "href", "http", "https")
                .addProtocols("use", "xlink:href", "http", "https");
    }


    /**
     * 创建宽松的SVG白名单
     * 允许更多SVG元素和属性，但仍排除潜在危险的内容
     *
     * @return Safelist 宽松SVG白名单
     */
    private static Safelist relaxedSvg() {
        return basicSvg()
                // 添加更多允许的元素
                .addTags("desc", "title", "metadata", "marker", "view", "foreignObject")
                // 添加更多允许的属性
                .addAttributes("svg", "class", "id", "style")
                .addAttributes("g", "id", "class", "style")
                .addAttributes("path", "class", "id", "style", "opacity", "transform")
                .addAttributes("rect", "class", "id", "style", "opacity", "transform")
                .addAttributes("circle", "class", "id", "style", "opacity", "transform")
                .addAttributes("ellipse", "class", "id", "style", "opacity", "transform")
                .addAttributes("line", "class", "id", "style", "opacity", "transform")
                .addAttributes("polyline", "class", "id", "style", "opacity", "transform")
                .addAttributes("polygon", "class", "id", "style", "opacity", "transform")
                .addAttributes("text", "class", "id", "style", "opacity", "transform", "font-weight", "font-style")
                .addAttributes("foreignObject", "x", "y", "width", "height", "requiredExtensions")
                // 添加foreignObject中的基本HTML元素
                .addTags("div", "span", "p", "br")
                .addAttributes("div", "class", "id", "style")
                .addAttributes("span", "class", "id", "style")
                .addAttributes("p", "class", "id", "style");
    }

    /**
     * 创建严格的SVG白名单
     * 仅允许最基础且安全的SVG元素和属性
     *
     * @return Safelist 严格SVG白名单
     */
    private static Safelist strictSvg() {
        return new Safelist()
                .addTags("svg", "path", "rect", "circle", "ellipse", "line", "polyline", "polygon")
                .addAttributes("svg", "xmlns", "width", "height", "viewBox")
                .addAttributes("path", "d", "fill", "stroke", "stroke-width")
                .addAttributes("rect", "x", "y", "width", "height", "fill", "stroke", "stroke-width")
                .addAttributes("circle", "cx", "cy", "r", "fill", "stroke", "stroke-width")
                .addAttributes("ellipse", "cx", "cy", "rx", "ry", "fill", "stroke", "stroke-width")
                .addAttributes("line", "x1", "y1", "x2", "y2", "stroke", "stroke-width")
                .addAttributes("polyline", "points", "fill", "stroke", "stroke-width")
                .addAttributes("polygon", "points", "fill", "stroke", "stroke-width");
    }
}
